﻿using System;
using System.Data;
using System.Data.Common;
using StackExchange.Profiling;
using StackExchange.Profiling.Data;
using System.Diagnostics;
using System.Configuration;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
using System.Web;

namespace Creative85.Framework.Data
{
    internal class DbLogger
    {
        [ThreadStatic]
        private static Stopwatch instance;
        private static object writerThread_locker = new object();
        private static Thread writerThread = null;

        private static ConcurrentQueue<Tuple<DateTime, long, string, DbParameterCollection>> queue = new ConcurrentQueue<Tuple<DateTime, long, string, DbParameterCollection>>();
        private static string LogPath = ConfigurationManager.AppSettings["LOG.SQL.Path"];
        private static int LogMilliseconds = int.Parse(ConfigurationManager.AppSettings["LOG.SQL.Milliseconds"] ?? "1000");
        private static int LogQueueMaxSize = int.Parse(ConfigurationManager.AppSettings["LOG.SQL.QueueMaxSize"] ?? "1000");
        private static EventWaitHandle LogThreadIdleEvent = new ManualResetEvent(false);

        public static void Start()
        {
            if (!string.IsNullOrWhiteSpace(LogPath))
            {
                if (instance == null)
                {
                    instance = new Stopwatch();
                }
                instance.Restart();
            }
        }
        public static void Stop()
        {
            if (instance == null) { return; }
            instance.Stop();
        }
        public static void Save(IDbCommand cmd)
        {
            if (instance == null) { return; }
            if (cmd == null) { return; }
            PreWriteLog(cmd);
            LoopWriteLogs();
        }

        private static void PreWriteLog(IDbCommand cmd)
        {
            if (instance.ElapsedMilliseconds < LogMilliseconds) { return; }
            if (queue.Count >= LogQueueMaxSize) { return; }

            var dt = DateTime.Now;
            var sql = cmd.CommandText;
            var parameters = cmd.Parameters as DbParameterCollection;
            queue.Enqueue(new Tuple<DateTime, long, string, DbParameterCollection>(dt, instance.ElapsedMilliseconds, sql, parameters));
        }
        private static void LoopWriteLogs()
        {
            if (queue.Count > 0) { LogThreadIdleEvent.Set(); }
            if (writerThread == null)
            {
                lock (writerThread_locker)
                {
                    if (writerThread == null)
                    {
                        writerThread = new Thread(new ThreadStart(() =>
                        {
                            var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogPath);
                            try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } }
                            catch { }
                            Tuple<DateTime, long, string, DbParameterCollection> item = null;
                            string lastfile = string.Empty;
                            StreamWriter writer = null;
                            while (LogThreadIdleEvent.WaitOne())
                            {
                                LogThreadIdleEvent.Reset();
                                while (queue.TryDequeue(out item))
                                {
                                    var name = item.Item1.ToString("yyyyMMdd") + ".log";
                                    if (lastfile != name)
                                    {
                                        if (writer != null) { writer.Flush(); writer.Dispose(); }

                                        var filepath = Path.Combine(path, name);
                                        try
                                        {
                                            writer = new StreamWriter(File.Open(filepath, FileMode.Append, FileAccess.Write, FileShare.Read), Encoding.UTF8);
                                        }
                                        catch
                                        {
                                            writer = null;
                                        }
                                    }
                                    if (writer != null)
                                    {
                                        var sql = item.Item3.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ').Replace('\t', ' ');
                                        var sqlparameters = new List<string>();
                                        if (item.Item4 != null)
                                        {
                                            foreach (DbParameter p in item.Item4)
                                            {
                                                sqlparameters.Add(string.Format("{0}={1}", p.ParameterName, p.Value));
                                            }
                                        }
                                        var content = string.Format("{0}\t{1}\t{2}\t{3}", item.Item1.ToString("HH:mm:ss.fffff"), item.Item2, sql, string.Join(" ", sqlparameters));
                                        writer.WriteLine(content);
                                    }
                                }
                                if (writer != null) { writer.Flush(); writer.Dispose(); writer = null; }
                            }
                        }))
                        {
                            IsBackground = true,
                            Name = "LOG.SQL.THREAD[" + LogPath + "]"
                        };
                        writerThread.Start();
                    }
                }
            }
        }
    }
    public class Db : NPoco.Database
    {
        /// <summary>
        /// ThreadStatic使每个线程只有一个实例（单例）。
        /// 单例是为了让同一个线程的所有操作可以被包含到一个事务中。
        /// </summary>
        [ThreadStatic]
        public static Db instance;
        [ThreadStatic]
        public static Db instance1;
        public bool IsDebug { get; set; }
        private Db(string conn, bool isDebug = true)
            : base(conn)
        {
            this.IsDebug = isDebug;
        }

        protected override IDbConnection OnConnectionOpened(IDbConnection conn)
        {
            if (this.IsDebug && conn is DbConnection)
            {
                return new ProfiledDbConnection((DbConnection)conn, MiniProfiler.Current);
            }
            else
            {
                return base.OnConnectionOpened(conn);
            }
        }

        private static readonly System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex("@(\\d+)");
        public override IDbCommand CreateCommand(IDbConnection connection, string sql, params object[] args)
        {
#if DEBUG
            if (args.Length > 0)
            {
                Debug.WriteLine(reg.Replace(sql, "{$1}"), args);
            }
            else
            {
                Debug.WriteLine(sql);
            }
#endif
            return base.CreateCommand(connection, sql, args);

        }

        private IDbCommand _cmd = null;
        protected override void OnExecutingCommand(IDbCommand cmd)
        {
            this._cmd = cmd;
            DbLogger.Start();
            base.OnExecutingCommand(cmd);
        }
        protected override void OnExecutedCommand(IDbCommand cmd)
        {
            base.OnExecutedCommand(cmd);
            DbLogger.Stop();
            DbLogger.Save(cmd);
            this._cmd = null;
        }
        protected override void OnException(Exception x)
        {
            DbLogger.Stop();
            DbLogger.Save(this._cmd);
            this._cmd = null;
            base.OnException(x);
        }
        public static string Type { get; set; }
        public static Db Instance
        {
            get
            {
                if (Type == "R7")
                {
                    instance = GetNewInstance("R7");
                    return instance;
                }
                else if (Type == "R8")
                {
                    instance1 = GetNewInstance("R8");
                    return instance1;
                }
                else
                {
                    throw new Exception("请选择数据库");
                }
            }
        }

        public static Db GetInstance(bool isDebug = true)
        {
            return new Db(Type, isDebug);
        }

        public static Db GetNewInstance(string connectionStringName, bool isDebug = true)
        {
            return new Db(connectionStringName, isDebug);
        }
    }
}
